﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.Data;
using System.ComponentModel.Composition.Hosting;

namespace IRM.Messaging
{
    public class MessageHandler
    {
        bool stoped;
        System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();
        Repository.IMessagesDataContext context;

        public MessageHandler(IDbConnection connection)
            : this(new Repository.SqlMessagesDataContext(connection))
        {
        }

        public MessageHandler(Repository.IMessagesDataContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            this.context = context;
            Initialize();
        }

        private void Initialize()
        {
            var catalog = new DirectoryCatalog(@".\");
            var container = new CompositionContainer(catalog);
            container.ComposeParts(this);

            worker.DoWork += new System.ComponentModel.DoWorkEventHandler(HandleQueue);
            worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
        }

        public void StartHandleAllMessages()
        {
            StartHandleAllMessages(false);
        }

        public void StartHandleAllMessages(bool runOnce)
        {
            worker.RunWorkerAsync(runOnce);
            stoped = false;
        }

        public void StopHandleAllMessages()
        {
            int i = 0;
            worker.CancelAsync();

            while (stoped || i < 11)
            {
                System.Threading.Thread.Sleep(500);
                i++;
            }
        }

        [ImportMany]
        public Lazy<IMessageHandler, IMessageHandlerCapabilities>[] Handlers { get; set; }

        private void HandleQueue(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            MessageQueue queue = new MessageQueue(context);

            while (worker.CancellationPending == false)
            {
                HandleAllMessages(queue);
                if ((bool)e.Argument)
                    return;
                System.Threading.Thread.Sleep(1500);
            }

        }

        private void HandleAllMessages(MessageQueue queue)
        {
            HandleResult result;
            Exception retryException = null;
            Message message = queue.Peek();

            while (message != null)
            {
                if (worker.CancellationPending) break;

                result = HandleResult.NotHandled;

                try
                {
                    foreach (var handler in Handlers)
                    {
                        if (handler.Metadata.Type == message.Topic)
                        {
                            HandleResult res = handler.Value.Handle(message);

                            if (res == HandleResult.Success && result != HandleResult.ScheduleForRetry)
                                result = res;
                            if (res == HandleResult.ScheduleForRetry)
                            {
                                result = res;
                                retryException = handler.Value.RetryException;
                                break;
                            }
                        }
                    }

                    switch (result)
                    {
                        case HandleResult.Success:
                            queue.Remove(message);
                            break;
                        case HandleResult.NotHandled:
                            queue.Fail(message, new NoAvailableMessageHandlerException(message.Id));
                            break;
                        case HandleResult.ScheduleForRetry:
                            queue.Reschedule(message, retryException);
                            break;
                        default:
                            break;
                    }

                }
                catch (StackOverflowException ex)
                {
                    queue.Fail(message, ex);
                    throw;
                }
                catch (OutOfMemoryException ex)
                {
                    queue.Fail(message, ex);
                    throw;
                }
                catch (Exception ex)
                {
                    queue.Fail(message, ex);
                }

                message = queue.Peek();
            }
        }

        void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            stoped = true;
            if (e.Error != null)
                throw e.Error;
        }

    }
}
